﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using AForge.Video;
using Damany.Imaging.Common;
using Damany.Imaging.PlugIns;
using Damany.Imaging.Processors;
using Damany.PC.Domain;
using Damany.Cameras;
using Damany.PortraitCapturer.DAL;
using MiscUtil;
using OpenCvSharp;

namespace RemoteImaging
{
    public class FaceSearchFacade
    {
        private readonly FaceComparer _faceComparer;
        private readonly MotionDetector _motionDetector;
        private readonly PortraitFinder _portraitFinder;
        private readonly IEnumerable<IFacePostFilter> _facePostFilters;
        private readonly IVideoSource _videoSource;
        private readonly IEventAggregator _eventAggregator;
        private readonly ConcurrentQueue<Bitmap> _motionFramesQueue = new ConcurrentQueue<Bitmap>();
        private System.Threading.Tasks.Task _faceSearchTask;
        private readonly AutoResetEvent _signal = new AutoResetEvent(false);
        private Damany.PC.Domain.CameraInfo _cameraInfo;
        private System.Threading.CancellationTokenSource _tokenSource = new CancellationTokenSource();
        private FaceCapture.FaceCountUpdater _faceCountUpdater = new FaceCapture.FaceCountUpdater();
        private System.Runtime.Serialization.Formatters.Binary.BinaryFormatter _formatter
            = new BinaryFormatter();

        private DateTime _lastFrameTime;


        public int CalcImageBufferPercent()
        {
            return (int)(_motionFramesQueue.Count / (float)MotionQueueSize * 100);
        }

        public void RaiseImageBufferChanged()
        {
            var arg = new EventArgs<int>(CalcImageBufferPercent());
            Messenger.Instance.RaiseFaceImageBufferChanged(arg);
        }

        public int MotionQueueSize { get; set; }

        public TimeSpan FrameInterval { get; set; }

        public FaceSearchFacade(Damany.Imaging.Processors.MotionDetector motionDetector,
                                Damany.Imaging.Processors.PortraitFinder portraitFinder,
                                IEnumerable<Damany.Imaging.Common.IFacePostFilter> facePostFilters,
                                Damany.Imaging.PlugIns.FaceComparer faceComparer,
                                AForge.Video.IVideoSource videoSource,
                                IEventAggregator eventAggregator)
        {
            if (videoSource == null) throw new ArgumentNullException("videoSource");
            _motionDetector = motionDetector;
            _portraitFinder = portraitFinder;
            _facePostFilters = facePostFilters;
            _videoSource = videoSource;
            _eventAggregator = eventAggregator;
            _faceComparer = faceComparer;

            FrameInterval = TimeSpan.FromMilliseconds(Properties.Settings.Default.FaceFrameIntervalInMs);

            Properties.Settings.Default.PropertyChanged +=
                (s, e) => FrameInterval = TimeSpan.FromMilliseconds(Properties.Settings.Default.FaceFrameIntervalInMs);

            MotionQueueSize = 10;
            _faceCountUpdater.CountToUpdate = 2;


        }


        public void Start()
        {
            _videoSource.NewFrame += JpegStreamNewFrame;
            StartFaceCapture();
        }

        private void StartFaceCapture()
        {
            if (_faceSearchTask == null)
            {
                _faceSearchTask =
                    Task.Factory.StartNew(
                        FaceSearchworkerWrapper,
                        _tokenSource.Token,
                        TaskCreationOptions.LongRunning,
                        TaskScheduler.Default
                        );
            }

            _faceComparer.Initialize();
            _faceComparer.Start();

            _faceComparer.Threshold = Properties.Settings.Default.RealTimeFaceCompareSensitivity;
            _faceComparer.Sensitivity = Properties.Settings.Default.LbpThreshold;
        }

        public void SignalToStop()
        {
            if (_videoSource != null)
            {
                _videoSource.NewFrame -= JpegStreamNewFrame;
                _videoSource.SignalToStop();

                _tokenSource.Cancel();
                _signal.Set();

                _faceComparer.SignalToStop();
            }
        }

        public void WaitForStop()
        {
            if (_videoSource != null)
            {
                _videoSource.SignalToStop();
                _videoSource.WaitForStop();
            }

            if (_faceSearchTask != null)
            {
                _signal.Set();
                _tokenSource.Cancel();
                try
                {
                    _faceSearchTask.Wait();
                }
                catch (AggregateException)
                {
                }

            }

            foreach (var frame in _motionFramesQueue)
            {
                frame.Dispose();
            }

            if (_faceComparer != null)
            {
                _faceComparer.WaitForStop();
            }
        }

        void JpegStreamNewFrame(object sender, NewFrameEventArgs eventArgs)
        {
            if (!Properties.Settings.Default.EnableFaceCapture)
            {
                return;
            }

            if (_motionFramesQueue.Count > MotionQueueSize)
            {
                return;
            }

            var now = DateTime.Now;
            if (now - _lastFrameTime < FrameInterval)
            {
                return;
            }

            _lastFrameTime = now;


            //System.Diagnostics.Debug.WriteLine("Face frame: " + now.Millisecond);

            var bmp = AForge.Imaging.Image.Clone(eventArgs.Frame);
            _motionFramesQueue.Enqueue(bmp);
            RaiseImageBufferChanged();


        }


        void FaceSearchworkerWrapper()
        {
            try
            {
                FaceSearchWorkerThread();

            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex);
            }
        }

        void FaceSearchWorkerThread()
        {
            while (true)
            {
                if (_tokenSource.Token.IsCancellationRequested) break;

                Bitmap bmp = null;
                if (_motionFramesQueue.TryDequeue(out bmp))
                {
                    RaiseImageBufferChanged();
                    if (_tokenSource.Token.IsCancellationRequested) break;

                    OpenCvSharp.IplImage ipl = null;

                    try
                    {
                        ipl = OpenCvSharp.IplImage.FromBitmap(bmp);
                    }
                    catch (Exception)
                    {
                        continue;
                    }


                    var frame = new Frame(ipl);
                    var grouped = true; //_motionDetector.ProcessFrame(frame);

                    if (grouped)
                    {
                        var motionFrames = new[] { frame };// _motionDetector.GetMotionFrames();
                        if (motionFrames != null)
                        {
                            foreach (var motionFrame in motionFrames)
                            {
                                var source = new MockFrameSource();
                                source.Id = 0;
                                motionFrame.CapturedFrom = source;

                                var portraits = _portraitFinder.ProcessFrame(motionFrame, _tokenSource.Token);

                                foreach (var portrait in portraits)
                                {
                                    var p = portrait;
                                    p.Frame.Oid = frame.Oid;
                                }

                                SavePortraits(portraits);


                                if (_eventAggregator != null)
                                {
                                    portraits.ForEach(p => _eventAggregator.PublishPortrait(p));
                                }

                                _faceCountUpdater.HandleFaceCountByHour(portraits);

                                if (_faceComparer != null)
                                {
                                    _faceComparer.ProcessPortraits(portraits);
                                }

                                portraits.ForEach(p => p.Dispose());
                                motionFrame.Dispose();
                            }

                            //motionFrames.ForEach(f => _repository.SaveFrame(f));
                        }
                    }

                    //frame.Dispose();
                }
                else
                {
                    Thread.Sleep(1000);
                }
            }
        }

        private void SavePortraits(IEnumerable<Portrait> portraits)
        {
            using (var uow = new DevExpress.Xpo.UnitOfWork())
            {
                foreach (var portrait in portraits)
                {
                    var path = SaveImage(portrait.GetIpl(), portrait.CapturedAt);
                    var p = new Damany.PortraitCapturer.DAL.DTO.Portrait(uow);
                    p.ImagePath = path;
                    p.FaceBounds = portrait.FaceBounds;
                    p.CaptureTime = portrait.CapturedAt;
                    p.ImageSourceId = portrait.DeviceId;

                    var frame = uow.GetObjectByKey(typeof(Damany.PortraitCapturer.DAL.DTO.Frame), portrait.Frame.Oid);
                    p.Frame = (Damany.PortraitCapturer.DAL.DTO.Frame)frame;
                }

                uow.CommitChanges();
            }
        }

        private static string SaveImage(IplImage image, DateTime captureTime)
        {
            var path = GetImagePath(captureTime);
            var dir = Path.GetDirectoryName(path);
            if (!Directory.Exists(dir))
            {
                Directory.CreateDirectory(dir);
            }
            image.SaveImage(path);

            return path;
        }

        private static string GetImagePath(DateTime dateTime)
        {
            var relativePath = string.Format("{0}\\{1}\\{2}\\{3}\\{4}.png",
                                                                 dateTime.Year,
                                                                 dateTime.Month,
                                                                 dateTime.Day,
                                                                 dateTime.Hour,
                                                                 Guid.NewGuid());

            var absPath = Path.Combine(Properties.Settings.Default.OutputPath, relativePath);

            return absPath;
        }
    }
}
